/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "operations.h"
#include "connection.h"

#define LOCK_DB_CONN(conn) Connection::Lock guard(conn)

namespace node {
	namespace rdb {

		bool PlainRequest::Execute(Connection* conn)
		{
			/// just do it
			LOCK_DB_CONN(conn);
			return conn->Execute(sql_);
		}

		Transaction::~Transaction()
		{
			while (!m_queue.empty())
			{
				delete m_queue.back();
				m_queue.pop_back();
			}
		}

		bool Transaction::Execute(Connection* conn)
		{
			if (m_queue.empty())
			{
				return true;
			}

			LOCK_DB_CONN(conn);

			conn->BeginTransaction();

			const int nItems = m_queue.size();
			for (int i = 0; i < nItems; ++i)
			{
				Operation* pStmt = m_queue[i];

				if (!pStmt->Execute(conn))
				{
					conn->RollbackTransaction();
					return false;
				}
			}

			return conn->CommitTransaction();
		}

		PreparedRequest::PreparedRequest(int nIndex, StmtParameters* arg) : m_nIndex(nIndex), m_param(arg)
		{
		}

		PreparedRequest::~PreparedRequest()
		{
			delete m_param;
		}

		bool PreparedRequest::Execute(Connection* conn)
		{
			LOCK_DB_CONN(conn);
			return conn->ExecuteStmt(m_nIndex, *m_param);
		}

		/// ---- ASYNC QUERIES ----

		bool Query::Execute(Connection* conn)
		{
			if (!m_callback || !m_queue)
			{
				return false;
			}

			LOCK_DB_CONN(conn);
			/// execute the query and store the result in the callback
			m_callback->SetResult(conn->Query(sql_));
			/// add the callback to the sql result queue of the thread it originated from
			m_queue->Add(m_callback);

			return true;
		}

		void ResultQueue::Update()
		{
			/// execute the callbacks waiting in the synchronization queue
			IQueryCallback* callback = NULL;
			while (Next(callback))
			{
				callback->Execute();
				delete callback;
			}
		}

		bool QueryHolder::Execute(IQueryCallback* callback, DelayThread* thread, ResultQueue* queue)
		{
			if (!callback || !thread || !queue)
			{
				return false;
			}

			/// delay the execution of the queries, sync them with the delay thread
			/// which will in turn resync on execution (via the queue) and call back
			QueryHolderEx* holderEx = new QueryHolderEx(this, callback, queue);
			thread->Delay(holderEx);
			return true;
		}

		bool QueryHolder::SetQuery(size_t index, const char* sql)
		{
			if (m_queries.size() <= index)
			{
				sLog.outError("Query index (" SIZEFMTD ") out of range (size: " SIZEFMTD ") for query: %s", index, m_queries.size(), sql);
				return false;
			}

			if (m_queries[index].first != NULL)
			{
				sLog.outError("Attempt assign query to holder index (" SIZEFMTD ") where other query stored (Old: [%s] New: [%s])",
					index, m_queries[index].first, sql);
				return false;
			}

			/// not executed yet, just stored (it's not called a holder for nothing)
			m_queries[index] = SqlResultPair(mangos_strdup(sql), (QueryResult*)NULL);
			return true;
		}

		bool SqlQueryHolder::SetPQuery(size_t index, const char* format, ...)
		{
			if (!format)
			{
				sLog.outError("Query (index: " SIZEFMTD ") is empty.", index);
				return false;
			}

			va_list ap;
			char szQuery[MAX_QUERY_LEN];
			va_start(ap, format);
			int res = vsnprintf(szQuery, MAX_QUERY_LEN, format, ap);
			va_end(ap);

			if (res == -1)
			{
				sLog.outError("SQL Query truncated (and not execute) for format: %s", format);
				return false;
			}

			return SetQuery(index, szQuery);
		}

		QueryResult* SqlQueryHolder::GetResult(size_t index)
		{
			if (index < m_queries.size())
			{
				/// the query strings are freed on the first GetResult or in the destructor
				if (m_queries[index].first != NULL)
				{
					delete[](const_cast<char*>(m_queries[index].first));
					m_queries[index].first = NULL;
				}
				/// when you get a result aways remember to delete it!
				return m_queries[index].second;
			}
			else
			{
				return NULL;
			}
		}

		void SqlQueryHolder::SetResult(size_t index, QueryResult* result)
		{
			/// store the result in the holder
			if (index < m_queries.size())
			{
				m_queries[index].second = result;
			}
		}

		SqlQueryHolder::~SqlQueryHolder()
		{
			for (size_t i = 0; i < m_queries.size(); ++i)
			{
				/// if the result was never used, free the resources
				/// results used already (getresult called) are expected to be deleted
				if (m_queries[i].first != NULL)
				{
					delete[](const_cast<char*>(m_queries[i].first));
					delete m_queries[i].second;
				}
			}
		}

		void SqlQueryHolder::SetSize(size_t size)
		{
			/// to optimize push_back, reserve the number of queries about to be executed
			m_queries.resize(size);
		}

		bool SqlQueryHolderEx::Execute(SqlConnection* conn)
		{
			if (!m_holder || !m_callback || !m_queue)
			{
				return false;
			}

			LOCK_DB_CONN(conn);
			/// we can do this, we are friends
			std::vector<SqlQueryHolder::SqlResultPair>& queries = m_holder->m_queries;
			for (size_t i = 0; i < queries.size(); ++i)
			{
				/// execute all queries in the holder and pass the results
				char const* sql = queries[i].first;
				if (sql) { m_holder->SetResult(i, conn->Query(sql)); }
			}

			/// sync with the caller thread
			m_queue->add(m_callback);

			return true;
		}

	}
}